home *** CD-ROM | disk | FTP | other *** search
/ Internet Publisher's Toolbox 2.0 / Internet Publisher's Toolbox.iso / internet / ntserver / wtsource / irinv.c < prev    next >
Encoding:
C/C++ Source or Header  |  1994-11-14  |  41.0 KB  |  1,198 lines

  1. /* WIDE AREA INFORMATION SERVER SOFTWARE:
  2.    No guarantees or restrictions.  See the readme file for the full standard
  3.    disclaimer.
  4.  
  5.    Brewster@think.com
  6. */
  7.  
  8. /* Copyright (c) CNIDR (see ../COPYRIGHT) */
  9.  
  10.  
  11. /* Change log:
  12.  * $Log: irinv.c,v $
  13.  * Revision 1.3  1993/09/23  03:00:07  pfeifer
  14.  * undid swap.
  15.  * Fixed bug with string compare (strcmp) for ISO-Chars
  16.  *
  17.  * Revision 1.2  1993/09/23  02:12:20  pfeifer
  18.  * swaped from_version and into_version merge_index_file
  19.  *
  20.  * Revision 1.1  1993/02/16  15:05:35  freewais
  21.  * Initial revision
  22.  *
  23.  * Revision 1.28  92/04/28  16:55:50  morris
  24.  * added boolean to serial engine
  25.  * 
  26.  * Revision 1.27  92/03/19  09:34:26  morris
  27.  * fixed the dictionary header to accurately indicate the number of blocks
  28.  * 
  29.  * Revision 1.26  92/03/01  16:11:16  brewster
  30.  * took out the analyze_hashtable_distribution
  31.  * 
  32.  * Revision 1.25  92/02/12  13:27:31  jonathan
  33.  * Added "$Log: irinv.c,v $
  34.  * Revision 1.3  1993/09/23  03:00:07  pfeifer
  35.  * undid swap.
  36.  * Fixed bug with string compare (strcmp) for ISO-Chars
  37.  *
  38.  * Revision 1.2  1993/09/23  02:12:20  pfeifer
  39.  * swaped from_version and into_version merge_index_file
  40.  *
  41.  * Revision 1.1  1993/02/16  15:05:35  freewais
  42.  * Initial revision
  43.  *
  44.  * Revision 1.28  92/04/28  16:55:50  morris
  45.  * added boolean to serial engine
  46.  * 
  47.  * Revision 1.27  92/03/19  09:34:26  morris
  48.  * fixed the dictionary header to accurately indicate the number of blocks
  49.  * 
  50.  * Revision 1.26  92/03/01  16:11:16  brewster
  51.  * took out the analyze_hashtable_distribution
  52.  * " so RCS will put the log message in the header
  53.  * 
  54. */
  55.  
  56. /* Inverted file accessor functions. 
  57.    
  58. Main functions:
  59.   finished_add_word
  60.  
  61. */
  62.  
  63.  
  64. #include <string.h> /* for memset() */
  65.  
  66. #include "cutil.h"
  67. #include "futil.h"
  68. #include "irhash.h"
  69. #include "hash.h"
  70. #include "panic.h"
  71. #include "irfiles.h"
  72. #include "irext.h"
  73.  
  74. /* ================== */
  75. /* === Index file === */
  76. /* ================== */
  77.  
  78. /*This is an implementation of the inverted file itself.
  79.  * An index_block_number is the position in the file that the
  80.  * entry starts.
  81.  * Index block 0 is the null pointer.
  82.  * The header contains the number of different words in the file.
  83.  *
  84.  */
  85.  
  86. /* the format of an index block is:
  87.  *  in the first byte:
  88.  *  INDEX_BLOCK_FULL_FLAG 
  89.  *    || INDEX_BLOCK_NOT_FULL_FLAG
  90.  *    || INDEX_BLOCK_DICTIONARY_FLAG
  91.  * if index_block_full_flag
  92.  *  next_index_block
  93.  *  index_block_size
  94.  *  stuff
  95.  *  (the number of valid entries is index_block_size - index_block_header_size)
  96.  * if index_block_not_full_flag
  97.  *  number_of_valid_entries
  98.  *  index_block_size
  99.  *  stuff
  100.  * if index_block_dictionary_flag
  101.  *  last_index_block      NEXT_INDEX_BLOCK_SIZE (0 when this is the last)
  102.  *  index_block_size      INDEX_BLOCK_SIZE_SIZE
  103.  *  number_of_occurances  NUMBER_OF_OCCURANCES_SIZE
  104.  *  word (followed by \n)
  105.  *
  106.  *
  107.  * "Stuff" is a list of word occurances of the format:
  108.  * (this is written in irhash.c and read in sersrch.c)
  109.  *  doc_id
  110.  *  character_position
  111.  *  weight
  112.  *  doc_id
  113.  *  character_position
  114.  *  weight 
  115.  *  etc.
  116.  *
  117.  *   It should be (probably in release 9):
  118.  *  doc_id (with high bit set)
  119.  *  weight
  120.  *  character_position
  121.  *  ...
  122.  *  character_position
  123.  *  doc_id
  124.  *  weight
  125.  *  character_position
  126.  *  etc.
  127.  *  
  128.  */
  129.  
  130. long 
  131. write_dictionary_index_block(number_of_occurances,word,stream)
  132. long number_of_occurances;
  133. char *word;
  134. FILE *stream;
  135. /* returns a pointer to the index block allocated */
  136. {
  137.   /* this assumes that the stream is positioned at the end */
  138.   long before_length = ftell(stream); /* file_length(stream); */
  139.   
  140.   long index_block_size = 
  141.     INDEX_BLOCK_FLAG_SIZE +
  142.       NEXT_INDEX_BLOCK_SIZE +
  143.     INDEX_BLOCK_SIZE_SIZE +
  144.       NUMBER_OF_OCCURANCES_SIZE +
  145.         strlen(word) +
  146.           strlen("\n"); /* this is done this way for PC's (necessary?) */
  147.   /* printf("writing dict entry to position %ld\n", before_length); */
  148.   /* grow the file */
  149.   /* in this implementation, the file is always filled by the fwrite,
  150.    * so it will grow itself.
  151.    * grow_file(stream, before_length + how_large);
  152.    * file length leaves the stream at the end, so we are all set.
  153.    * s_fseek(stream, before_length, SEEK_SET);
  154.    */
  155.   write_bytes(INDEX_BLOCK_DICTIONARY_FLAG, INDEX_BLOCK_FLAG_SIZE,
  156.           stream);  
  157.   write_bytes(0L, NEXT_INDEX_BLOCK_SIZE, stream);
  158.   write_bytes(index_block_size, INDEX_BLOCK_SIZE_SIZE, stream);
  159.  
  160.   write_bytes(number_of_occurances, NUMBER_OF_OCCURANCES_SIZE, 
  161.           stream);
  162.   if(strlen(word) > MAX_WORD_LENGTH) /* for debugging */
  163.     waislog(WLOG_HIGH, WLOG_ERROR, 
  164.         "Word: '%s' is too long (%ld characters) for some reason.",
  165.         word, strlen(word));
  166.  
  167.   fprintf(stream, "%s\n", word); 
  168.  
  169.   /* just to check
  170.   { long after_length = ftell(stream);
  171.     if(after_length - before_length != index_block_size){
  172.       waislog(WLOG_HIGH, WLOG_ERROR, 
  173.           "dictionary entry size is %ld, and we thought %ld",
  174.           after_length - before_length, index_block_size);
  175.     }
  176.   }
  177.   */
  178.  
  179.   return(before_length);
  180. }
  181.  
  182. #ifdef everneeded
  183. static long modify_dictionary_index_block 
  184.   _AP((long index_block,long last_index_block,long number_of_occurances,
  185.        FILE* stream));
  186.  
  187. static long modify_dictionary_index_block(index_block,last_index_block,number_of_occurances,stream)
  188. long index_block;   
  189. long last_index_block;
  190. long number_of_occurances;
  191. FILE* stream;
  192.  
  193.      /* this does not allow one to change the word itself, only the entries 
  194.     around it.  It will panic if the index_block is not a valid block.
  195.     This returns the the stream to pointing at the end of the file.
  196.     */
  197. {
  198.   s_fseek(stream, index_block, SEEK_SET);
  199.   if(INDEX_BLOCK_DICTIONARY_FLAG != read_bytes(INDEX_BLOCK_FLAG_SIZE, stream))
  200.     panic("the index block %ld is not a legal dictionary block",
  201.       index_block);
  202.   write_bytes(number_of_occurances, NUMBER_OF_OCCURANCES_SIZE, 
  203.           stream);
  204.   write_bytes(last_index_block, NEXT_INDEX_BLOCK_SIZE, stream);
  205.   read_bytes(INDEX_BLOCK_SIZE_SIZE, stream); /* ignore it */
  206.   write_bytes(number_of_occurances, NUMBER_OF_OCCURANCES_SIZE, 
  207.           stream);
  208.   s_fseek(stream, 0L, SEEK_END);
  209. }
  210.  
  211. #endif /* def everneeded */
  212.  
  213. static long next_dictionary_index_block
  214.   _AP((long* index_block_size,long* number_of_occurances,char* word,
  215.        FILE* stream));
  216.  
  217. static long
  218. next_dictionary_index_block(index_block_size,number_of_occurances,word,stream)
  219. long *index_block_size;
  220. long *number_of_occurances;
  221. char *word;
  222. FILE *stream;
  223. {
  224.   /* this read the dictionary index block from the index stream.
  225.      It assumes the stream is positioned at the start of a dictionary
  226.      block, and will return non-0 if it is not.
  227.      returns 0 if it succeeds.
  228.      returns -1 if it is at the of a file.
  229.      returns -2 if it read something strange.
  230.      Always sets word length to 0 if it fails. */
  231.   long index_block_flag;
  232.   char temp[MAX_WORD_LENGTH + 2];
  233.   
  234.   word[0] = '\0';
  235.  
  236.   index_block_flag = read_bytes(INDEX_BLOCK_FLAG_SIZE, stream);
  237.   if(index_block_flag == EOF){
  238.     /* not an error, it is the way it knows it is done 
  239.     waislog(WLOG_HIGH, WLOG_ERROR, "Hit the end of the inverted file too early");
  240.     */
  241.     return(-1);
  242.   }
  243.   if(index_block_flag != INDEX_BLOCK_DICTIONARY_FLAG){
  244.     waislog(WLOG_HIGH, WLOG_ERROR, 
  245.         "Did not find a dictionary entry, flag is %ld at file position %ld", 
  246.         index_block_flag, ftell(stream) - INDEX_BLOCK_FLAG_SIZE);
  247.     return(-2);
  248.   }
  249.   (void)read_bytes(NEXT_INDEX_BLOCK_SIZE, stream);
  250.   *index_block_size = read_bytes(INDEX_BLOCK_SIZE_SIZE, stream);
  251.   *number_of_occurances = read_bytes(NUMBER_OF_OCCURANCES_SIZE,
  252.                      stream);
  253.   fgets(temp, MAX_WORD_LENGTH + 2, stream); /* 2 is for the \n and '\0' */
  254.  
  255.   /* trim the \n */
  256.   if(temp[strlen(temp) - 1] == '\n'){
  257.     temp[strlen(temp) - 1] = '\0';
  258.   }
  259.   strcpy(word, temp);
  260.   /* printf("Merging word '%s'\n", word); */
  261.  
  262.   return(0);
  263. }
  264.  
  265. long
  266. read_dictionary_index_block(index_block,
  267.                 last_index_block,
  268.                 index_block_size,
  269.                 number_of_occurances,
  270.                 word,
  271.                 stream)
  272. long index_block;
  273. long *last_index_block;
  274. long *index_block_size;
  275. long *number_of_occurances;
  276. char *word;
  277. FILE *stream;
  278. {
  279.   /* this read the dictionary index block from the index stream.
  280.      returns 0 if it succeeds. */
  281.   long answer;
  282.   s_fseek(stream, index_block, SEEK_SET);
  283.   if(0 > (answer = next_dictionary_index_block(index_block_size,
  284.                             number_of_occurances,
  285.                             word,
  286.                             stream)))
  287.     panic("read dictionary block %ld failed", index_block);
  288.  
  289.   *last_index_block = 0;
  290.   return(answer);
  291. }
  292.  
  293.  
  294. long allocate_index_block(how_large,stream)
  295. long how_large;
  296. FILE* stream;
  297. {
  298.   /* This returns a pointer for an index block.
  299.      It creates the space and writes the header.
  300.      how_large includes the header.
  301.      Returns the block_number (the byte address of first byte in the block */
  302.   /* this assumes that the stream is positioned at the end */
  303.   long before_length = ftell(stream); /* file_length(stream); */
  304.   /* grow the file */
  305.   /* in this implementation, the file is always filled by the fwrite,
  306.    * so it will grow itself.
  307.    * grow_file(stream, before_length + how_large);
  308.    * file length leaves the stream at the end, so we are all set.
  309.    * s_fseek(stream, before_length, SEEK_SET);
  310.    */
  311.   write_bytes(INDEX_BLOCK_FULL_FLAG, /* in this version all are full */
  312.           INDEX_BLOCK_FLAG_SIZE,
  313.           stream);
  314.   write_bytes(0L, NEXT_INDEX_BLOCK_SIZE, stream);
  315.   write_bytes(how_large, INDEX_BLOCK_SIZE_SIZE, stream);
  316.   return(before_length);
  317. }
  318.  
  319. #ifdef testing
  320.  
  321. static void scan_index_blocks _AP((char* filename,boolean verbose));
  322.  
  323. static void scan_index_blocks(filename,verbose)
  324. char *filename;
  325. boolean verbose;
  326. {
  327.   /* this is a debugging routine for checking the inverted index file */
  328.   long current_index_block = INDEX_HEADER_SIZE;
  329.   FILE *stream = s_fopen(filename, "rb");
  330.   long length = file_length(stream);
  331.   
  332.   s_fseek(stream, current_index_block, SEEK_SET);
  333.   printf("File length %ld\n", length);
  334.  
  335.   while(current_index_block < length){
  336.     long flag = read_bytes(INDEX_BLOCK_FLAG_SIZE, stream);
  337.     long next_index_block = read_bytes(NEXT_INDEX_BLOCK_SIZE, stream);
  338.     long index_block_size = read_bytes(INDEX_BLOCK_SIZE_SIZE, stream);
  339.     if(flag == INDEX_BLOCK_DICTIONARY_FLAG){
  340.       long last_index_block;
  341.       long index_block_size;
  342.       long number_of_occurances;
  343.       char word[MAX_WORD_LENGTH + 1];
  344.       if(0 > read_dictionary_index_block(current_index_block,
  345.                   &last_index_block,
  346.                   &index_block_size,
  347.                   &number_of_occurances,
  348.                   word,
  349.                   stream))
  350.     panic("read dictionary index block failed");
  351.       if(verbose)
  352.     printf("%5ld: size %3ld Dictionary entry '%s', number of occurances %ld last block %ld\n",
  353.            current_index_block, index_block_size, word, 
  354.            number_of_occurances, next_index_block);
  355.     }
  356.     else if(flag == INDEX_BLOCK_NOT_FULL_FLAG){
  357.       if(verbose)
  358.     printf("%5ld: size %3ld Not full, valid entries %ld\n",
  359.            current_index_block, index_block_size, next_index_block);
  360.     }
  361.     else if(flag == INDEX_BLOCK_FULL_FLAG){
  362.       if(verbose)
  363.     printf("%5ld: size %3ld full block, next block %ld\n",
  364.          current_index_block, index_block_size, next_index_block);
  365.     }
  366.     else 
  367.       panic("bad entry %ld (ftell %ld), flag was %ld", 
  368.         current_index_block, 
  369.         ftell(stream), flag);
  370.     current_index_block += index_block_size;
  371.     s_fseek(stream, current_index_block, SEEK_SET);
  372.   }
  373.   s_fclose(stream);
  374. }   
  375.  
  376. #endif /* def testing */
  377.  
  378. #define COPY_BLOCK_BUFFER_LENGTH 1000
  379.  
  380. static long copy_stream _AP((FILE* from_stream,FILE* to_stream,long n));
  381.  
  382. static long copy_stream(from_stream,to_stream,n)
  383. FILE *from_stream;
  384. FILE *to_stream;
  385. long n;
  386. {
  387.   char buffer[COPY_BLOCK_BUFFER_LENGTH];
  388.   while(n > 0){
  389.     /* keep reading until we are done */
  390.     long amount_read = fread(buffer, sizeof(char), 
  391.                  MIN(n, COPY_BLOCK_BUFFER_LENGTH),
  392.                  from_stream);
  393.     if(amount_read == 0 || amount_read == EOF)
  394.       return(-1);
  395. #ifdef WIN32
  396.     if(amount_read != (long)fwrite(buffer, sizeof(char), amount_read, to_stream))
  397. #else
  398.     if(amount_read != fwrite(buffer, sizeof(char), amount_read, to_stream))
  399. #endif
  400.       return(-1);
  401.     n -= amount_read;
  402.   }
  403.   return(0);
  404. }
  405.  
  406.  
  407. static void merge_blocks _AP((char* word,FILE* from_stream_1,
  408.                   FILE* from_stream_2,FILE* to_stream));
  409.  
  410. static void merge_blocks(word,from_stream_1,from_stream_2,to_stream)
  411. char* word;
  412. FILE *from_stream_1;
  413. FILE *from_stream_2;
  414. FILE *to_stream;
  415. /* puts from_stream_1 first into to_stream then from_stream_2*/
  416. {
  417.   long flag;
  418.   long index_block_size;
  419.   long other_block_size;
  420.     
  421.   flag = read_bytes(INDEX_BLOCK_FLAG_SIZE, from_stream_1);
  422.   (void)read_bytes(NEXT_INDEX_BLOCK_SIZE, from_stream_1);
  423.   index_block_size = read_bytes(INDEX_BLOCK_SIZE_SIZE, from_stream_1);
  424.  
  425.   if(flag == EOF) return;
  426.   if(flag != INDEX_BLOCK_FULL_FLAG)
  427.     panic("the next index block is not a full block");
  428.  
  429.   flag = read_bytes(INDEX_BLOCK_FLAG_SIZE, from_stream_2);
  430.   (void)read_bytes(NEXT_INDEX_BLOCK_SIZE, from_stream_2);
  431.   other_block_size = read_bytes(INDEX_BLOCK_SIZE_SIZE, from_stream_2);
  432.   if(flag != INDEX_BLOCK_FULL_FLAG)
  433.     panic("the next index block is not a full block");
  434.  
  435.   write_bytes(flag, INDEX_BLOCK_FLAG_SIZE, to_stream);
  436.   write_bytes(0L, NEXT_INDEX_BLOCK_SIZE, to_stream);
  437.   if((index_block_size + other_block_size) >
  438.      (1L << (INDEX_BLOCK_SIZE_SIZE * 8))){
  439.     /* then the block is too large to be represented in the 
  440.        index_block_size_size.  This routine takes the radical step to
  441.        eliminate it completely.  The "right" thing to do is to
  442.        chain blocks, but I dont think it is worth it since this should not
  443.        happen very often.  If it does happen often then bump up
  444.        INDEX_BLOCK_SIZE_SIZE. */
  445.     waislog(WLOG_LOW, WLOG_INFO,
  446.         "Can not merge the index block for %s, since it would create one that is too big.  Deleting it.",word);
  447.     write_bytes(INDEX_BLOCK_HEADER_SIZE, INDEX_BLOCK_SIZE_SIZE, to_stream);
  448.     s_fseek(from_stream_1, index_block_size - INDEX_BLOCK_HEADER_SIZE, SEEK_CUR);
  449.     s_fseek(from_stream_2, other_block_size - INDEX_BLOCK_HEADER_SIZE, SEEK_CUR);
  450.   }
  451.   else{ /* copy away */
  452.     write_bytes(index_block_size + other_block_size - INDEX_BLOCK_HEADER_SIZE, 
  453.         INDEX_BLOCK_SIZE_SIZE, to_stream);
  454.     copy_stream(from_stream_1, to_stream, index_block_size - INDEX_BLOCK_HEADER_SIZE);
  455.     copy_stream(from_stream_2, to_stream, other_block_size - INDEX_BLOCK_HEADER_SIZE);
  456.   }
  457. }
  458.  
  459. static void make_dummy_block _AP((FILE* from_stream_1,FILE* from_stream_2,
  460.                   FILE* to_stream));
  461.  
  462. static void make_dummy_block(from_stream_1,from_stream_2,to_stream)
  463. FILE *from_stream_1;
  464. FILE *from_stream_2;
  465. FILE *to_stream;
  466. /* deletes block from_stream_1 and from_stream_2 and makes a 0 length
  467.    block on to_stream */
  468. {
  469.   long flag;
  470.   long index_block_size;
  471.   long other_block_size;
  472.     
  473.   flag = read_bytes(INDEX_BLOCK_FLAG_SIZE, from_stream_1);
  474.   (void)read_bytes(NEXT_INDEX_BLOCK_SIZE, from_stream_1);
  475.   index_block_size = read_bytes(INDEX_BLOCK_SIZE_SIZE, from_stream_1);
  476.  
  477.   if(flag == EOF) return;
  478.   if(flag != INDEX_BLOCK_FULL_FLAG)
  479.     panic("the next index block is not a full block");
  480.  
  481.   flag = read_bytes(INDEX_BLOCK_FLAG_SIZE, from_stream_2);
  482.   (void)read_bytes(NEXT_INDEX_BLOCK_SIZE, from_stream_2);
  483.   other_block_size = read_bytes(INDEX_BLOCK_SIZE_SIZE, from_stream_2);
  484.   if(flag != INDEX_BLOCK_FULL_FLAG)
  485.     panic("the next index block is not a full block");
  486.  
  487.   write_bytes(flag, INDEX_BLOCK_FLAG_SIZE, to_stream);
  488.   write_bytes(0L, NEXT_INDEX_BLOCK_SIZE, to_stream);
  489.   write_bytes(INDEX_BLOCK_HEADER_SIZE, INDEX_BLOCK_SIZE_SIZE, to_stream);
  490.   s_fseek(from_stream_1, index_block_size - INDEX_BLOCK_HEADER_SIZE, SEEK_CUR);
  491.   s_fseek(from_stream_2, other_block_size - INDEX_BLOCK_HEADER_SIZE, SEEK_CUR);
  492. }
  493.  
  494. static void copy_block _AP((FILE* from_stream,FILE* to_stream));
  495.  
  496. static void copy_block(from_stream,to_stream)
  497. FILE *from_stream;
  498. FILE *to_stream;
  499. /* copies an index block from one stream to another */
  500. { /* copies an index block from from_stream to to_stream */
  501.   long flag;
  502.   long next_index_block;
  503.   long index_block_size;
  504.  
  505.   flag = read_bytes(INDEX_BLOCK_FLAG_SIZE, from_stream);
  506.   next_index_block = read_bytes(NEXT_INDEX_BLOCK_SIZE, from_stream);
  507.   index_block_size = read_bytes(INDEX_BLOCK_SIZE_SIZE, from_stream);
  508.  
  509.   if(flag == EOF) return;
  510.   write_bytes(flag, INDEX_BLOCK_FLAG_SIZE, to_stream);
  511.   write_bytes(next_index_block, NEXT_INDEX_BLOCK_SIZE, to_stream);
  512.   write_bytes(index_block_size, INDEX_BLOCK_SIZE_SIZE, to_stream);
  513.   /* copy the real stuff */
  514.   copy_stream(from_stream, to_stream, 
  515.           index_block_size - INDEX_BLOCK_HEADER_SIZE);
  516. }
  517.  
  518. /* ================== */
  519. /* === Merge File === */
  520. /* ================== */
  521.  
  522. static boolean merge_index_file _AP((long from_version, long into_version,
  523.                   database* db,
  524.                   boolean generate_dictionary));
  525.  
  526. /* Merges a index file (from_version) into another (into_version).
  527.    Returns true if successful */
  528.  
  529. static boolean merge_index_file(into_version, from_version, 
  530.                 db,generate_dictionary)
  531. long into_version;
  532. long from_version;
  533. database* db;
  534. boolean generate_dictionary;
  535. /* 
  536.    This is done by merging both into version -1 and then, 
  537.    renames it to into_version, then deletes from_version. */
  538. {
  539.   char input_filename_1[MAX_FILENAME_LEN]; /* into_version file */
  540.   char input_filename_2[MAX_FILENAME_LEN]; /* from_version file */
  541.   char output_filename[MAX_FILENAME_LEN];  /* version -1 */
  542.  
  543.   FILE *input_stream_1;
  544.   FILE *input_stream_2;
  545.   FILE *output_stream;
  546.   char input_word_1[MAX_WORD_LENGTH + 1];
  547.   char input_word_2[MAX_WORD_LENGTH + 1];
  548.  
  549.   if(generate_dictionary) {
  550.     if(into_version == -2)
  551.       waislog(WLOG_MEDIUM, WLOG_INDEX,
  552.           "Merging version %ld into master version and generating dictionary.",
  553.           from_version);
  554.     else
  555.       waislog(WLOG_MEDIUM, WLOG_INDEX,
  556.           "Merging version %ld into %ld and generating dictionary.",
  557.           from_version, into_version);
  558.   }
  559.   else {
  560.     waislog(WLOG_MEDIUM, WLOG_INDEX, "Merging version %ld and %ld", 
  561.         into_version, from_version);
  562.   }
  563.  
  564.   index_filename_with_version(into_version, input_filename_1, db);
  565.   index_filename_with_version(from_version, input_filename_2, db); 
  566.   index_filename_with_version(-1L, output_filename, db); 
  567.   
  568.  
  569.   if(NULL == (input_stream_1 = s_fopen(input_filename_1, "rb")))
  570.     return(false);
  571.  
  572.   if(NULL == (input_stream_2 = s_fopen(input_filename_2, "rb")))
  573.     return(false);
  574.  
  575.   if(NULL == (output_stream = s_fopen(output_filename, "wb")))
  576.     return(false);
  577.  
  578.   {
  579.     long index_block_size_1;
  580.     long number_of_occurances_1;
  581.     long index_block_size_2;
  582.     long number_of_occurances_2;
  583.  
  584.     /* leave room for the number_of_words in the output */
  585.     s_fseek(output_stream, INDEX_HEADER_SIZE, SEEK_SET);
  586.  
  587.     /* read the number of words in the 2 input files, this is the maximum 
  588.        number of words that can be in the dictionary,  It is used by 
  589.        init_dict_file to set asside space for the dictionary_header_block.
  590.        It is very likley that some of the words are duplicates, so the actual
  591.        size of the header_block will be smaller.  But we never know until we
  592.        do the merge, so we have to give enough space just in case.
  593.        NOTE - in the case where generate_dictioary is false, we still need
  594.        to do this because we need to skip the header_size in each of the
  595.        input streams 
  596.      */
  597.     db->number_of_words = read_bytes(INDEX_HEADER_SIZE,input_stream_1) +
  598.                           read_bytes(INDEX_HEADER_SIZE,input_stream_2);
  599.  
  600.     if (generate_dictionary) /* allocate the header block if necessary */
  601.      { init_dict_file_for_writing(db);
  602.      }
  603.  
  604.     /* now that the dictioanry_header_block is allocated, we can start counting
  605.        for real */
  606.     db->number_of_words = 0; 
  607.  
  608.     if(-1 > next_dictionary_index_block(&index_block_size_1,
  609.                        &number_of_occurances_1,
  610.                        input_word_1,
  611.                        input_stream_1))
  612.       panic("Read of dictionary block failed in file %s",
  613.         input_filename_1);
  614.     if(-1 > next_dictionary_index_block(&index_block_size_2,
  615.                 &number_of_occurances_2,
  616.                 input_word_2,
  617.                 input_stream_2))
  618.       panic("Read of dictionary block failed in file %s",
  619.         input_filename_2);
  620.     while(true){
  621.       long strlen_1 = strlen(input_word_1);
  622.       long strlen_2 = strlen(input_word_2);
  623.       long compare = strcmp(input_word_1, input_word_2); /* +1 if 1 is bigger */
  624.       if (!strlen_1) {          /* Make it work for iso chars */
  625.         if (!strlen_2) compare = 0;
  626.         else           compare = -1;
  627.       }
  628.       if((0 == strlen_1) && 
  629.      (0 == strlen_2)){
  630.     /* printf("Done with merge\n"); */
  631.     /* then we are done. */
  632.     break;
  633.       }
  634.       else if((0 != strlen_1) && (0 != strlen_2) && (0 == compare)){
  635.     /* they are equal */
  636.     /* printf("Merging word %s and %s\n", input_word_1, input_word_2); */
  637.     write_dictionary_index_block(number_of_occurances_1 +
  638.                      number_of_occurances_2, 
  639.                      input_word_1, 
  640.                      output_stream);
  641.     if(generate_dictionary){
  642.       add_word_to_dictionary(input_word_1, ftell(output_stream), 
  643.                  number_of_occurances_1 +
  644.                  number_of_occurances_2, 
  645.                  db);
  646.     }
  647.     db->number_of_words++;
  648.     /* copy file 1 first.  this assumes that file 1 was indexed before
  649.        file 2 */
  650.     if((number_of_occurances_1+number_of_occurances_2) > MAX_OCCURANCES){
  651.       /* too many already, just make a dummy (0 length) */
  652.       if((number_of_occurances_1 < MAX_OCCURANCES) &&
  653.          (number_of_occurances_2 < MAX_OCCURANCES)) {
  654.         /* only print the first time */
  655.         waislog(WLOG_MEDIUM, WLOG_INDEX,
  656.             "Deleting word '%s' since it has %ld occurences (limit %ld).",
  657.             input_word_1,number_of_occurances_1+number_of_occurances_2,
  658.             MAX_OCCURANCES);
  659.       }
  660.       make_dummy_block(input_stream_1, input_stream_2, output_stream);
  661.     }
  662.     else{
  663.       merge_blocks(input_word_1,input_stream_1,input_stream_2,
  664.                output_stream); 
  665.     }
  666.     if(-1 > next_dictionary_index_block(&index_block_size_1,
  667.                     &number_of_occurances_1,
  668.                     input_word_1,
  669.                     input_stream_1))
  670.       panic("Read of dictionary block failed in file %s",
  671.         input_filename_1);
  672.  
  673.     if(-1 > next_dictionary_index_block(&index_block_size_2,
  674.                     &number_of_occurances_2,
  675.                     input_word_2,
  676.                     input_stream_2))
  677.       panic("Read of dictionary block failed in file %s",
  678.         input_filename_2);
  679.       }
  680.       else if(((0 == strlen_1) && (0 != strlen_2) && (0 > compare)) ||
  681.           ((0 != strlen_1) && (0 != strlen_2) && (0 < compare))){
  682.     /* write from block 2 */
  683.     /* printf("From block 2: writing word '%s' not '%s'\n", 
  684.        input_word_2, input_word_1);  */
  685.     write_dictionary_index_block(number_of_occurances_2, input_word_2,
  686.                      output_stream);
  687.     if(generate_dictionary){
  688.       add_word_to_dictionary(input_word_2, ftell(output_stream), 
  689.                  number_of_occurances_2, db);
  690.     }
  691.     db->number_of_words++;
  692.     copy_block(input_stream_2, output_stream);
  693.     if(-1 > next_dictionary_index_block(&index_block_size_2,
  694.                     &number_of_occurances_2,
  695.                     input_word_2,
  696.                     input_stream_2))
  697.       panic("Read of dictionary block failed in file %s",
  698.         input_filename_2);
  699.       }
  700.       else{
  701.     /* write from block 1 */
  702.     /* printf("From block 1: writing word '%s' not '%s'\n", 
  703.        input_word_1, input_word_2); */
  704.     write_dictionary_index_block(number_of_occurances_1, input_word_1,
  705.                      output_stream);
  706.     if(generate_dictionary){
  707.       add_word_to_dictionary(input_word_1, ftell(output_stream), 
  708.                  number_of_occurances_1, db);
  709.     }
  710.     db->number_of_words++;
  711.     copy_block(input_stream_1, output_stream);
  712.     if(-1 > next_dictionary_index_block(&index_block_size_1,
  713.                        &number_of_occurances_1,
  714.                        input_word_1,
  715.                        input_stream_1))
  716.       panic("Read of dictionary block failed in file %s",
  717.         input_filename_1);
  718.       }
  719.     }
  720.   }
  721.  
  722.   /* write out the number of words */
  723.   s_fseek(output_stream, 0L, SEEK_SET);
  724.   write_bytes(db->number_of_words, INDEX_HEADER_SIZE, output_stream);
  725.  
  726.   s_fclose(input_stream_1);
  727.   s_fclose(input_stream_2);
  728.   s_fclose(output_stream);
  729.   /* check resulting file */    
  730.   /* check_index_file(output_filename); */
  731.   /* scan_index_blocks(output_filename); */
  732.  
  733.   /* delete the input files */
  734.   remove(input_filename_1);
  735.   remove(input_filename_2);
  736.   /* These next two calls result in the renaming of the new files ontop of 
  737.      the old
  738.      files. This is the only critical time in which if an online update is 
  739.      happening (the index files are being changed while the 
  740.      queries are being answered).  If a query comes along in another
  741.      process and opens the .inv file after the rename is done, but before
  742.      the finished_add_word_to_dictionary has the change to rename the .dct
  743.      file and therefore opens the old version of the .dct file, then the
  744.      files will be out of synch. */
  745.   if (0 != rename(output_filename, input_filename_1))
  746.     waislog(WLOG_HIGH, WLOG_ERROR,
  747.         "could not rename file %s to %s",
  748.         output_filename, input_filename_1);
  749.   if(generate_dictionary)
  750.     finished_add_word_to_dictionary(db);
  751.     
  752. #ifdef THINK_C
  753.   /* set the mac file type to INDX */
  754.   setFileType(input_filename_1, WAIS_INDEX_FILE_TYPE, CREATOR);
  755. #endif /* THINK_C */
  756.  
  757.   return(true);
  758. }
  759.  
  760. /* only works on positive, non-zero numbers, and is slow to boot, yippie */
  761. static long logcount _AP((long number));
  762. static long logcount(number)
  763. long number;
  764. {
  765.   long answer = 0;
  766.   for( ; number > 0; number = number >> 1)
  767.     answer++;
  768.   return(answer);
  769. }
  770.  
  771. /*
  772.  * Originally, the index files produced while building an index were
  773.  * merged together using a normal binary merge sort.  For example, if
  774.  * there were 8 files (0 through 7) to be merged, they were merged
  775.  * in the following order, after all the indexing was finished:
  776.  *
  777.  * 0+1 -> 0  2+3 -> 2  4+5 -> 4  6+7 -> 6
  778.  * 0+2 -> 0  4+6 -> 4
  779.  * 0+4 -> 0
  780.  *
  781.  * The only problem with this is that it means that all of the index
  782.  * files have to stay around until the entire database has been
  783.  * indexed.  This means that the amount of disk space needs
  784.  * temporarily during the indexing grows linearly with the amount of
  785.  * data being indexed.
  786.  *
  787.  * The idea of this routine is to reduce the amount of temporary disk
  788.  * space used by doing whatever merging it can, DURING the indexing
  789.  * rather than after it.  The routine gets called whenever an index
  790.  * file has just been flushed, and it figures out what merging needs
  791.  * to be performed and does it.
  792.  *
  793.  * The merging done by this routine ends up being the same as the
  794.  * merging done by the old routine at the end of indexing.  However,
  795.  * it happens in a different order.  For example, once again, if the
  796.  * total number of index files were 8, this is what would happen (a
  797.  * number by itself means that it was flushed; number+number->number
  798.  * refers to a merge):
  799.  * 
  800.  * 0 1 0+1->0
  801.  * 2 3 2+3->2 0+2->0
  802.  * 4 5 4+5->4
  803.  * 6 7 6+7->6 4+6->4 0+4->0
  804.  *
  805.  * You should be able to convince yourself relatively quickly that the
  806.  * result of this is that the amount of space needed temporarily
  807.  * during indexing grows logarithmically with the amount of data being
  808.  * indexed, rather than linearly.  What's more, no extra time is required.
  809.  *
  810.  * There is one complication.  When the total number of files being
  811.  * indexed is an even power of two, the pattern of merging is regular,
  812.  * as demonstrated above.  However, if the total number of files is
  813.  * not an even power of two, things are a bit complicated.  For
  814.  * example, if there were 11 files instead of 8, we'd have to add this
  815.  * to the end of the list given above:
  816.  *
  817.  * 8 9 8+9->8
  818.  * 10 8+10->8 0+8->0
  819.  * 
  820.  * The algorithm for determining what merges to perform after every
  821.  * second index file, when the index file just flushed is not the last
  822.  * one, is simple.  However, the algorithm for determining what to do
  823.  * on the last file flushed, whether or not it's a power of two, is a
  824.  * bit more complicated.
  825.  *
  826.  * The first algorithm:
  827.  *
  828.  * splitter := the number of the file just indexed (starting from 1)
  829.  * if splitter is odd
  830.  *   then return
  831.  * low = splitter - 2  // counting index files from 0
  832.  * high = splitter - 1 // ditto
  833.  * twotothe = 1
  834.  * while splitter is even do
  835.  *   merge files low and high into low
  836.  *   high := high - twotothe
  837.  *   twotothe := twotothe * 2
  838.  *   low := low - twotothe
  839.  *   splitter := splitter / 2
  840.  * done
  841.  *
  842.  * The translation from this into the code below should be somewhat
  843.  * obvious.  What is NOT obvious, however, is all the stuff with
  844.  * "target", "realhigh" and "where_to_stop", and with the extra loop
  845.  * wrapped around the code above.  The point of all that is to handle
  846.  * the second case mentioned above, when we're done indexing and need
  847.  * to merge everything together.  It works by pretending doing a
  848.  * series of merges of the first type described above, from the actual
  849.  * last index file until the next power of two higher than it.  At
  850.  * each pretended series, only those merges for which the low and high
  851.  * index files actually still exist are performed.
  852.  *
  853.  * In the degenerate case of the second algorithm, when the last index
  854.  * file being flushed is an even power of two, the outer loop only
  855.  * runs once and the behavior is the same as in the first algorithm
  856.  * described above.
  857.  *
  858.  * I hope that explains things adequately :-).
  859.  */
  860.  
  861. #ifndef END_MERGE
  862.  
  863. static void do_intermediate_merging _AP((database *db, boolean completely));
  864.  
  865. static void do_intermediate_merging(db, completely)
  866. database *db;
  867. boolean completely;
  868. {
  869.   int last = db->index_file_number - 1;
  870.   int twotothe, low, high, where_to_stop, target, realhigh = last, splitter;
  871.   char filename[MAX_FILENAME_LEN];
  872.   char filename2[MAX_FILENAME_LEN];
  873.  
  874.   if (last % 2 == 0) {
  875.     if (! completely) {
  876.       return;
  877.     }
  878.     else {
  879.       for (where_to_stop = 1; where_to_stop < last + 1;
  880.        where_to_stop *= 2) /* empty */;
  881.     }
  882.   }
  883.   else {
  884.     if (completely) {
  885.       for (where_to_stop = 1; where_to_stop < last + 1;
  886.        where_to_stop *= 2) /* empty */;
  887.     }
  888.     else {
  889.       where_to_stop = last + 1;
  890.     }
  891.   }
  892.  
  893.   for (target = ((last % 2) == 0) ? last + 2 : last + 1;
  894.        target <= where_to_stop; target += 2) {
  895.  
  896.     low = target - 2;
  897.     high = target - 1;
  898.     twotothe = 1;
  899.     splitter = target;
  900.       
  901.     while (((splitter % 2) == 0) && (low >= 0)) {
  902.       if (low < realhigh) {
  903.     if (! merge_index_file(low, (high > realhigh) ? realhigh : high, db,
  904.                    completely && (! low) &&
  905.                    !probe_file(index_filename(filename, db)))) {
  906.       panic("Error in merging file %ld into %ld", low,
  907.         (high > last) ? last : high);
  908.     }
  909.     realhigh = low;
  910.       }
  911.       high -= twotothe;
  912.       twotothe *= 2;
  913.       low -= twotothe;
  914.       splitter /= 2;
  915.     }
  916.   }
  917.  
  918.   if (completely) {
  919.     if (probe_file(index_filename(filename, db))) {
  920.       merge_index_file(-2L, 0L, db, true);
  921.     }
  922.     else if (last == 0) {
  923.       touch_file(index_filename_with_version(1, filename, db));
  924.       merge_index_file(0L, 1, db, true);
  925.       /* rename 0 into the final name */
  926.       index_filename_with_version(0L, filename2, db);
  927.       index_filename(filename, db);
  928.       if (0!=rename(filename2,filename))
  929.         waislog(WLOG_HIGH, WLOG_ERROR,
  930.             "could not rename file %s to %s",
  931.             filename2, filename);
  932.     }
  933.   }
  934. }
  935.  
  936. #endif  /* #ifndef END_MERGE */ 
  937.     
  938. static void merge_index_files _AP((database* db));
  939.  
  940. static void merge_index_files(db)
  941. database *db;
  942. /* this merges all the temporary inverted files into a large on 
  943.  * and creates a dictionary.
  944.  * This is done in a logrithmic merge of the files.
  945.  * An n-ary merge would be faster of course, but what the heck... 
  946.  */
  947. {
  948.   /* this version does it two at a time */
  949.   char filename[MAX_FILENAME_LEN];
  950.   char filename2[MAX_FILENAME_LEN];
  951.   long level;
  952.   long final_level;
  953.   long number_of_files_to_merge = db->index_file_number;
  954.   /* at level 0, merge 0,1->0; 2,3->2; 4,5->4; 6,7->6; 
  955.      at level 1, merge 0,2->0; 4,6->4;
  956.      at level 2, merge 0,4->0;
  957.      stop.
  958.      If there is only 1 file, then dont merge (final_level will be -1),
  959.      the dictionary will be generated by merge after the for loop.
  960.      */
  961.   final_level = logcount(number_of_files_to_merge - 1) - 1;
  962.   for(level = 0; level <= final_level; level++){
  963.     long into_version;
  964.     for(into_version = 0; 
  965.     into_version < number_of_files_to_merge; 
  966.     into_version += (2 << level)){
  967.       long from_version = into_version + (1 << level);
  968.       if(from_version < number_of_files_to_merge){
  969.     boolean generate_dictionary; /* if this is the final level and 
  970.                     there is no .inv file then yes */
  971.     if(level == final_level){
  972.       if(probe_file(index_filename(filename, db)))
  973.         generate_dictionary = false;
  974.       else generate_dictionary = true;
  975.     }
  976.     else{ 
  977.       generate_dictionary = false;
  978.     }
  979.     
  980.     /* printf("Level %d (out of %d) merging into %d from %d\n", 
  981.            level, final_level, into_version, from_version); */
  982.     if(false == merge_index_file(into_version, from_version, db,
  983.                      generate_dictionary))
  984.       panic("Error in merging file %ld into %ld", 
  985.         from_version, into_version);
  986.       }
  987.     }
  988.   } /* done merging */
  989.   if(probe_file(index_filename(filename, db))){
  990.     /* if there is a .inv file, then we are adding to an existing db. merge */ 
  991.     /* do this by merging straight into the final using the special -2 version
  992.        for the .inv file */
  993.     merge_index_file(-2L, 0L, db, true);    
  994.   }
  995.   else if(number_of_files_to_merge == 1){
  996.     /* then we have to generate a dictionary for this one file. 
  997.        create a dummy file in 1, then merge that while making a dictionary
  998.        */
  999.     touch_file(index_filename_with_version(1, filename, db));
  1000.     merge_index_file(0L, 1, db, true);
  1001.     /* rename 0 into the final name */
  1002.     index_filename_with_version(0L,filename2, db);
  1003.     index_filename(filename, db);
  1004.     if (0!=rename(filename2,filename))
  1005.       waislog(WLOG_HIGH, WLOG_ERROR,
  1006.         "could not rename file %s to %s",
  1007.         filename2, filename);
  1008.   }
  1009. }    
  1010.  
  1011.  
  1012. /* ============================================ */
  1013. /* ===  Flushing the memory version of the  === */
  1014. /* ===  word hashtable to disk files        === */
  1015. /* ============================================ */
  1016.  
  1017. static void flush_word_entry_to_file _AP((hash_entry* wrd_entry,
  1018.                       database* db));
  1019.  
  1020. static void flush_word_entry_to_file(wrd_entry,db)
  1021. hash_entry* wrd_entry;
  1022. database* db;
  1023. {
  1024.   /* In this version, each word_entry is made into an index block.
  1025.      This means that there may be many small index blocks if the memory
  1026.      can not hold many files worth of data.  This approach was taken 
  1027.      for simplicities sake and it might be the best approach if there
  1028.      is a small vocabulary.
  1029.      
  1030.      This assumes that the index stream is positioned at the end.
  1031.      */
  1032.  
  1033.   if(wrd_entry->number_of_occurances > MAX_OCCURANCES){
  1034.     /* there are too many of this word, do nothing.
  1035.      * this may result in some having been written before, or 
  1036.      * no occurances of this word might be recorded.  Is this right?
  1037.      * if the first MAX_OCCURANCES want to be recored, then
  1038.      * add this clause to this condition:
  1039.      *     && wrd_entry->starting_block_number != 0
  1040.      */
  1041.     return;
  1042.   }
  1043.   if((wrd_entry->current_memory_ptr - wrd_entry->memory_ptr +
  1044.       INDEX_BLOCK_HEADER_SIZE) >=
  1045.      (1L << (8 * INDEX_BLOCK_SIZE_SIZE)))
  1046.     return; /* there are too many entries to store away, therefore throw it all away */
  1047.   
  1048.   if(NULL == wrd_entry->memory_ptr){
  1049.     /* not entries to write out */
  1050.     return;
  1051.   }
  1052.   if(0 == (wrd_entry->current_memory_ptr - wrd_entry->memory_ptr)){
  1053.     return;         /* there are no word entries to write */
  1054.   }
  1055.   /* printf("Flushing word %s\n", wrd_entry->key); */
  1056.   /* if this is the entry for this word, the put in the dictionary
  1057.      entry in the index file */
  1058.   write_dictionary_index_block(wrd_entry->number_of_occurances,
  1059.                    wrd_entry->key,
  1060.                    db->index_stream);
  1061.  
  1062.   db->number_of_words++;
  1063.  
  1064.   /* allocate and write the new block */
  1065.   allocate_index_block(wrd_entry->current_memory_ptr -
  1066.                wrd_entry->memory_ptr +
  1067.                INDEX_BLOCK_HEADER_SIZE,
  1068.                db->index_stream);
  1069.   /* cprintf(PRINT_AS_INDEXING, "New block number: %ld\n", new_block); */
  1070. #ifdef WIN32
  1071.   if((wrd_entry->current_memory_ptr - wrd_entry->memory_ptr) !=
  1072.      (long)fwrite(wrd_entry->memory_ptr, 1L, (wrd_entry->current_memory_ptr -
  1073.                     wrd_entry->memory_ptr),
  1074.         db->index_stream))
  1075. #else
  1076.   if((wrd_entry->current_memory_ptr - wrd_entry->memory_ptr) !=
  1077.      fwrite(wrd_entry->memory_ptr, 1L, (wrd_entry->current_memory_ptr -
  1078.                     wrd_entry->memory_ptr),
  1079.         db->index_stream))
  1080. #endif
  1081.     panic("Write failed");
  1082.   /* free the memory for the block written out */
  1083.   free_word_occurance_block(wrd_entry->memory_ptr);
  1084.   wrd_entry->memory_ptr =  NULL;
  1085.   wrd_entry->current_memory_ptr = NULL;
  1086.   wrd_entry->memory_size = 0;
  1087.   wrd_entry->current_doc_id = 0;
  1088. }
  1089.   
  1090.  
  1091. /* This flushes all of the memory version of the word hashtable to disk.
  1092.  * this is called when the hashtable is filling up or we have 
  1093.  * accumulated enough words.  If completely is true, then it will merge 
  1094.  * all the intermediate files.
  1095.  */
  1096. void flush_memory_hashtable_to_disk(db,completely)
  1097. database* db;
  1098. boolean completely;
  1099. {
  1100.   /* map over the memory word hashtable and write the entries to disk */
  1101.   long i;
  1102.   char filename[1000];
  1103.  
  1104.   /* analyze the hash distribution 
  1105.   analyze_hashtable_distribution(db->the_word_memory_hashtable);
  1106.   */
  1107. #ifdef WIN32
  1108.   /* Close the index file if it is open */
  1109.   if (db->index_stream!=NULL) {
  1110.     s_fclose(db->index_stream);
  1111.   }
  1112. #endif
  1113.  
  1114.   db->index_stream = 
  1115.     s_fopen(index_filename_with_version(db->index_file_number++, filename, db),
  1116.         "wb");
  1117.   if(NULL == db->index_stream)
  1118.     panic("Could not open file %s to insert index", 
  1119.       index_filename_with_version(db->index_file_number, filename, db));
  1120.  
  1121.   waislog(WLOG_MEDIUM, WLOG_INDEX,
  1122.       "Flushing %ld different words, %ld total words to disk...", 
  1123.       hashtable_count(db->the_word_memory_hashtable),
  1124.       db->number_of_words_in_hashtable);
  1125.   db->number_of_words = 0;
  1126.   write_bytes(0l, INDEX_HEADER_SIZE, db->index_stream);
  1127.  
  1128.   sort_hashtable(db->the_word_memory_hashtable);
  1129.   for(i = 0; i < hashtable_count(db->the_word_memory_hashtable); i++){
  1130.     flush_word_entry_to_file(&db->the_word_memory_hashtable->contents[i], db);
  1131.   }
  1132.  
  1133.   /* write out the number of entries into the index_file header */
  1134.   s_fseek(db->index_stream, 0L, SEEK_SET);
  1135.   write_bytes(db->number_of_words, INDEX_HEADER_SIZE, db->index_stream);
  1136.   s_fclose(db->index_stream);
  1137.  
  1138.   /* since everything is written out, we can flush these. */
  1139.   flush_word_occurance_buffers(); 
  1140.   clr_hashtable(db->the_word_memory_hashtable);
  1141.   db->number_of_words_in_hashtable = 0;
  1142.  
  1143.   /* add the stopwords to the index for the next session. */
  1144.   add_stop_words(db->the_word_memory_hashtable);
  1145.  
  1146.     waislog(WLOG_MEDIUM, WLOG_INDEX,
  1147.       "Done flushing version %ld", db->index_file_number - 1);
  1148.   /* scan_index_blocks(filename); */
  1149.  
  1150.  
  1151.  
  1152. #ifdef END_MERGE
  1153.   if(completely){
  1154.     waislog(WLOG_MEDIUM, WLOG_INDEX,
  1155.         "Merging %ld files",
  1156.         db->index_file_number);
  1157.     merge_index_files(db);
  1158.     waislog(WLOG_MEDIUM, WLOG_INDEX,
  1159.         "Done merging.");
  1160.   }
  1161. #else
  1162.  
  1163.   do_intermediate_merging(db, completely);
  1164.  
  1165. #endif   /* #ifdef END_MERGE */
  1166. }
  1167.  
  1168.  
  1169. long init_add_word(db, hashtable_size, flush_after_n_words)
  1170.      database *db;
  1171.      long hashtable_size;
  1172.      long flush_after_n_words;
  1173. {
  1174.     char fn[256];
  1175.   if(NULL != db->the_word_memory_hashtable)
  1176.     free_hashtable(db->the_word_memory_hashtable);
  1177.   db->the_word_memory_hashtable =
  1178.     make_hashtable(0, hashtable_size, sizeof(hash_entry));
  1179.   db->flush_after_n_words = flush_after_n_words;
  1180.   add_stop_words(db->the_word_memory_hashtable);
  1181.  strcpy( fn,db->database_file );
  1182.   strcat( fn,synonym_ext );
  1183.   syn_ReadFile( fn,&db->syn_Table,&db->syn_Table_Size );
  1184.  
  1185.   return(0);
  1186. }
  1187.  
  1188.  
  1189.  
  1190. long finished_add_word(db)
  1191. database *db;
  1192. {
  1193.   flush_memory_hashtable_to_disk(db,true);
  1194.   syn_Free( db->syn_Table,&db->syn_Table_Size );
  1195.   return(0); /* successful */
  1196. }
  1197.  
  1198.